#include <asm/io.h>
#include <asm/system.h>
#include <cnix/types.h>
#include <cnix/string.h>
#include <cnix/kernel.h>
#include <cnix/driver.h>
#include <cnix/mm.h>
#include <cnix/net.h>

#define NE2K_IOBASE	0x0300
#define NE2K_IRQ	3

#define NE2K_CR		0x00
#define NE2K_PSTART	0x01
#define NE2K_PSTOP	0x02
#define NE2K_BNRY	0x03
#define NE2K_TSR	0x04
#define NE2K_TPSR	0x04
#define NE2K_TBCR0	0x05
#define NE2K_NCR	0x05
#define NE2K_TBCR1	0x06
#define NE2K_ISR	0x07
#define NE2K_CURR	0x07 /* in page 1 */
#define NE2K_RSAR0	0x08
#define NE2K_CRDA0	0x08
#define NE2K_RSAR1	0x09
#define NE2K_CRDA1	0x09
#define NE2K_RBCR0	0x0a
#define NE2K_RBCR1	0x0b
#define NE2K_RSR	0x0c
#define NE2K_RCR	0x0c
#define NE2K_TCR	0x0d
#define NE2K_CNTR0	0x0d
#define NE2K_DCR	0x0e
#define NE2K_CNTR1	0x0e
#define NE2K_IMR	0x0f
#define NE2K_CNTR2	0x0f
#define NE2K_DATA	0x10

#define REGADDR(n)	(NE2K_IOBASE + n)

#define RECV_PAGE_START	0x4c
#define RECV_PAGE_STOP	0x80

#define SEND_PAGE_START	0x40
#define SEND_PAGE_STOP  RECV_PAGE_START

/* INDEX FOR COMMAND REGISTER */
#define CR_STP 0		// STOP
#define CR_STA	1		// START
#define CR_TXP	2		// initialize to TRANSMIT

#define CR_RD0	3		
#define CR_RD1	4
#define CR_RD2	5

#define CR_RDMA_READ	(1 << CR_RD0)
#define CR_RDMA_WRITE	(2 << CR_RD0)
#define CR_SEND_PACKAGE (3 << CR_RD0)

#define CR_PS0	6
#define CR_PS1	7

#define PRX 0x01
#define PTX 0x02
#define RXE 0x04
#define TXE 0x08
#define OVW 0x10
#define CNT 0x20
#define RDC 0x40
#define RST 0x80

#define MIN_ETHER_PACK_LEN	46

typedef struct _ether_ne2k
{
	LOCAL_NETIF

	// Same as ether_netif_t
	unsigned long io_base_addr;
	int irq_no;
	BOOLEAN (*transmit)(struct _ether_ne2k *netif);
	int (*ioctl)(netif_t *netif, int flag);
} ether_ne2k_t;

static int ne2k_recv_next_pkg;

static void usepg(int page)
{
	unsigned char cmd;

	cmd = inb_p(REGADDR(NE2K_CR));
	cmd &= 0x3b; /* ~(page | TXP) */
	cmd |= page << CR_PS0;
	outb_p(cmd, REGADDR(NE2K_CR));
}

static mac_addr_t my_mac_addr;

static void get_my_mac(void)
{
	int i;

	usepg(0);

	outb_p(0x00, REGADDR(NE2K_RSAR1));
	outb_p(0x00, REGADDR(NE2K_RSAR0));
	outb_p(0x00, REGADDR(NE2K_RBCR1));
	outb_p(0x0c, REGADDR(NE2K_RBCR0));

	/* STA | RD0(remote read) */
	outb_p(0x0a, REGADDR(NE2K_CR));

	for (i = 0; i < 6; i++)
	{
		my_mac_addr.mac_addr[i] = inb_p(REGADDR(NE2K_DATA));

		/* discard one byte */
		inb_p(REGADDR(NE2K_DATA));
	}
}

static void show_my_mac(unsigned char mac_addr[6])
{
	int i;

	printk("MAC ADDR: ");

	for (i = 0; i < 6; i++)
	{
		printk("%x", mac_addr[i]);

		if (i >= 5)
		{
			break;
		}

		printk("-");
	}

	printk("\n");
}

static void send_command(unsigned short addr, int len, unsigned char cmd)
{
	outb_p((addr >> 8), REGADDR(NE2K_RSAR1));
	outb_p((addr & 0xff), REGADDR(NE2K_RSAR0));
	outb_p((len >> 8), REGADDR(NE2K_RBCR1));
	outb_p((len & 0xff), REGADDR(NE2K_RBCR0));

	outb_p(cmd, REGADDR(NE2K_CR));
}

static unsigned char send_pages[2];
static int xmt_buffer_index;
static int free_xmt_buffer_number;

static BOOLEAN ne2k_transmit_packet(ether_ne2k_t *netif)
{
	skbuff_t * buf, * skb;
	list_t * pos, * tmp;
	unsigned char * buffer;
	// int status;
	int total;

	disable_irq(NE2K_IRQ);

	if (free_xmt_buffer_number < 1)
	{
		enable_irq(NE2K_IRQ);
		return FALSE;
	}

	free_xmt_buffer_number--;

	// get one skbuff from out queue
	tmp = netif->out_queue.next;
	list_del1(tmp);	

	skb = list_entry(tmp, pkt_list, skbuff_t);
	
	if (skb->dgram_len > RTX_BUFF_SIZE)
	{
		PANIC("BUG: can not happend!\n");
	}

	usepg(0);

	total = 0;

	if (skb->dgram_len < MIN_ETHER_PACK_LEN + ETHER_LNK_HEAD_LEN)
	{
		total = MIN_ETHER_PACK_LEN + ETHER_LNK_HEAD_LEN;
	}
	else
	{
		total = skb->dgram_len;
	}

	send_command(
		send_pages[xmt_buffer_index] << 8,
		skb->dgram_len,
		CR_RDMA_WRITE | (1 << CR_STA)
		);

	foreach (tmp, pos, &skb->list)
		int i, written;

		buf = list_entry(tmp, list, skbuff_t);
		if (!(buffer = sk_get_buff_ptr(buf)))
		{
			break;
		}

		written = buf->dlen - buf->data_ptr;

		for (i = 0; i < written; i++)
		{
			outb_p(buffer[buf->data_ptr + i], REGADDR(NE2K_DATA));
		}
	endeach(&skb->list);

	while (skb->dgram_len < total)
	{
		outb_p(0, REGADDR(NE2K_DATA));
		skb->dgram_len++;
	}

	/* clear remote byte count register */
	outb_p(0x00, REGADDR(NE2K_RBCR0));
	outb_p(0x00, REGADDR(NE2K_RBCR1));
	
	outb_p(send_pages[xmt_buffer_index], REGADDR(NE2K_TPSR));
	outb_p((skb->dgram_len >> 8), REGADDR(NE2K_TBCR1));
	outb_p((skb->dgram_len & 0xff), REGADDR(NE2K_TBCR0));

	// while (inb_p(NE2K_CR) & 0x04);
	
	// Issue send package command
	outb_p(
		CR_SEND_PACKAGE | (1 << CR_TXP) | (1 << CR_STA), 
		REGADDR(NE2K_CR)
		);

	skb_free_packet(skb);
	enable_irq(NE2K_IRQ);

	// status = inb_p(NE2K_TSR);

	xmt_buffer_index = (xmt_buffer_index + 1) % 2;

	return TRUE;
}

static int ne2k_ioctl(netif_t *netif, int flag)
{
	return 1;
}


static BOOLEAN check_receive(ether_ne2k_t * pdev)
{
	unsigned char curr;
	unsigned char bound;
	int i, len;
	unsigned char pack_header[4];
	skbuff_t *skb;
	unsigned char *data;
	
	usepg(1);
	curr = inb_p(REGADDR(NE2K_CURR));

	while (ne2k_recv_next_pkg != curr)
	{
		usepg(0);
		send_command(
			ne2k_recv_next_pkg << 8,
			4,
			CR_RDMA_READ | (1 << CR_STA)
			);

		for (i = 0; i < 4; i++)
		{
			pack_header[i] = inb_p(REGADDR(0x10));
		}

		len = (pack_header[3] << 8);
		len |= pack_header[2];

		if (!(pack_header[0] & 0x01)
			|| (pack_header[1] > RECV_PAGE_STOP)
			|| (pack_header[1] < RECV_PAGE_START))
		{
			printk(
				"rcv error %x %x\n",
				pack_header[0],
				pack_header[1]
				);

			usepg(1);

			curr = inb_p(REGADDR(NE2K_CURR));
			if ((curr > RECV_PAGE_STOP) || (curr < RECV_PAGE_START))
			{
				curr = RECV_PAGE_START;
			}

			outb_p(curr, REGADDR(NE2K_CURR));

			return FALSE;
		}

		if ((len < (46 + ETHER_LNK_HEAD_LEN + 4)
			|| (len > ETHER_MTU + ETHER_LNK_HEAD_LEN + 4)))
		{
			printk("rcv error\n");
			return FALSE;
		}

		len -= 4; /* cut the CRC bytes */

		send_command(
			(ne2k_recv_next_pkg << 8) + 4,
			len,
			CR_RDMA_READ | (1 << CR_STA)
			);

		skb = skb_alloc(len, 0);
		if (skb)
		{
			data = sk_get_buff_ptr(skb);
			skb->data_ptr = 0;
			skb->dgram_len = skb->dlen - skb->data_ptr;
			skb->flag |= SK_PK_HEAD_DATA;

			for (i = 0; i < len; i++)
			{
				data[i] = inb_p(REGADDR(NE2K_DATA));
			}

			skb->netif = (netif_t *)pdev;

			list_add_tail(&pdev->in_queue, &skb->pkt_list);
		}

		ne2k_recv_next_pkg = pack_header[1];

		if ((bound = ne2k_recv_next_pkg - 1) < RECV_PAGE_START)
		{
			bound = RECV_PAGE_STOP - 1;
		}
		outb_p(bound, REGADDR(NE2K_BNRY));
		
		usepg(1);
		curr = inb_p(REGADDR(NE2K_CURR));
	}

	return TRUE;
}

static void ne2k_intr(struct regs_t * regs)
{
	unsigned char status;
	ether_ne2k_t * pdev;

	if (!(pdev = (ether_ne2k_t *)get_netif_by_irq(regs->index)))
	{
		printk("regs->index = %d\n", regs->index);
		return;
	}

	usepg(0);

	status = inb_p(REGADDR(NE2K_ISR));

	/* clear all interrupts */
	outb_p(0xff, REGADDR(NE2K_ISR));

	/* enable all interrupts */
	outb_p(0x1b, REGADDR(NE2K_IMR));

	if (status & ~(PRX | PTX | RDC))
	{
		printk("ne2k interrupt caused by (%x)\n", status);
		return;
	}

	if (status & PRX)
	{
		check_receive(pdev);
	}

	if (status & PTX)
	{
		free_xmt_buffer_number++;

		if (!list_empty(&pdev->out_queue))
		{
			ne2k_transmit_packet(pdev);
		}
	}

	if (status & RDC)
	{
		/* just ingore */
	}

	raise_bottom(NETIF_B);
}

static BOOLEAN setup_irq_handler(int irq)
{
	put_irq_handler(irq, ne2k_intr);

	return TRUE;
}

int ne2k_init(void)
{
	unsigned char cmd;
	ether_ne2k_t * pdev;

	/* STP | RD2(abort/complete Remote DMA) */
	outb_p(0x21, REGADDR(NE2K_CR));

	cmd = inb_p(REGADDR(NE2K_CR));
	if (cmd == 0x21)
	{
		printk("ne2k iobase %x irq: %d\n", NE2K_IOBASE, NE2K_IRQ);
	}
	else
	{
		return -1;
	}

	send_pages[0] = SEND_PAGE_START;
	send_pages[1] = SEND_PAGE_START + 6;
	xmt_buffer_index = 0;
	free_xmt_buffer_number = 2;
	
	ne2k_recv_next_pkg = RECV_PAGE_START + 1;
	
	setup_irq_handler(NE2K_IRQ);

	/* LS | FT1 */
	outb_p(0xc8, REGADDR(NE2K_DCR));

	/* clear remote byte count register */
	outb_p(0x00, REGADDR(NE2K_RBCR0));
	outb_p(0x00, REGADDR(NE2K_RBCR1));

	/* AB | AM */
	outb_p(0xcc, REGADDR(NE2K_RCR));

	/* loop mode 1 */
	outb_p(0x02, REGADDR(NE2K_TCR));

	outb_p(RECV_PAGE_START, REGADDR(NE2K_BNRY));	/* boundary */

	outb_p(RECV_PAGE_START, REGADDR(NE2K_PSTART));	/* page start */
	outb_p(RECV_PAGE_STOP, REGADDR(NE2K_PSTOP));	/* page stop */

	/* clear all interrupts */
	outb_p(0xff, REGADDR(NE2K_ISR));

	/* enable all interrupts */
	outb_p(0x1b, REGADDR(NE2K_IMR));

	get_my_mac();
	show_my_mac(my_mac_addr.mac_addr);

	outb_p(0x61, REGADDR(NE2K_CR));

	/* set my mac address */
	outb_p(my_mac_addr.mac_addr[0], REGADDR(0x01));
	outb_p(my_mac_addr.mac_addr[1], REGADDR(0x02));
	outb_p(my_mac_addr.mac_addr[2], REGADDR(0x03));
	outb_p(my_mac_addr.mac_addr[3], REGADDR(0x04));
	outb_p(my_mac_addr.mac_addr[4], REGADDR(0x05));
	outb_p(my_mac_addr.mac_addr[5], REGADDR(0x06));

	/* set the multicast address */
	outb_p(0xff, REGADDR(0x08));
	outb_p(0xff, REGADDR(0x09));
	outb_p(0xff, REGADDR(0x0a));
	outb_p(0xff, REGADDR(0x0b));
	outb_p(0xff, REGADDR(0x0c));
	outb_p(0xff, REGADDR(0x0d));
	outb_p(0xff, REGADDR(0x0e));
	outb_p(0xff, REGADDR(0x0f));

	/* CURR == BOUNDARY + 1 */
	outb_p(ne2k_recv_next_pkg, REGADDR(NE2K_CURR));

	outb_p(0x22, REGADDR(NE2K_CR));

	/* FU | CDH | OWC */
	outb_p(0xe0, REGADDR(NE2K_TCR));

	if (!(pdev = (ether_ne2k_t *)kmalloc(sizeof(ether_ne2k_t), 0)))
	{
		PANIC("no memory for ether ne2k32\n");
	}

	ether_init((ether_netif_t *)pdev, NE2K_IOBASE, NE2K_IRQ, &my_mac_addr);

	pdev->flag |= NETIF_RUNNING | NETIF_UP;

	pdev->transmit = ne2k_transmit_packet;
	pdev->ioctl = ne2k_ioctl;

	return 0;
}

#include <cnix/modconfig.h>

#ifdef PCNET32_MODULE

int module_enter(void)
{
	return 1;
}

int module_exit(void)
{
	return 1;
}

#endif
